package com.osell.selection.widget;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Point;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import com.da.lon.wang.xlg.R;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

public class SilkProduceView extends FrameLayout {

    private List<View> allViews;
    private List<View> views;
    private List<View> normalViews;
    private List<View> randomViews;
    private Map<View, Point> calculatedViews;

    public SilkProduceView(@NonNull Context context) {
        super(context);
        init();
    }

    public SilkProduceView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public SilkProduceView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public SilkProduceView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private static boolean checkBump(View v0, View v, Point p0, Point p) {
        int L0 = p0.x;
        int T0 = p0.y;
        int R0 = p0.x + v0.getMeasuredWidth();
        int B0 = p0.y + v0.getMeasuredHeight();

        int L = p.x;
        int T = p.y;
        int R = p.x + v.getMeasuredWidth();
        int B = p.y + v.getMeasuredHeight();

        return B >= T0 && R >= L0 && L <= R0 && T <= B0;
    }

    private void init() {
        allViews = new ArrayList<>();
        views = new ArrayList<>();
        normalViews = new ArrayList<>();
        randomViews = new ArrayList<>();
        calculatedViews = new HashMap<>();
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
        return new LayoutParams(lp);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof FrameLayout.LayoutParams;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        allViews.clear();
        views.clear();
        normalViews.clear();
        randomViews.clear();
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (lp.layoutMode == LayoutParams.NORMAL) {
                normalViews.add(child);
            } else {
                randomViews.add(child);
            }
            allViews.add(child);
        }
        for (View view : calculatedViews.keySet()) {
            if (!allViews.contains(view)) {
                views.add(view);
            }
        }
        for (Iterator<Map.Entry<View, Point>> it = calculatedViews.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<View, Point> item = it.next();
            if (views.contains(item.getKey())) {
                it.remove();
            }
        }

        if (!normalViews.isEmpty()) {
            final int parentLeft = getPaddingLeft();
            final int parentRight = right - left - getPaddingRight();

            final int parentTop = getPaddingTop();
            final int parentBottom = bottom - top - getPaddingBottom();
            for (View child : normalViews) {
                if (child.getVisibility() != GONE) {
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                    final int width = child.getMeasuredWidth();
                    final int height = child.getMeasuredHeight();

                    int childLeft;
                    int childTop;

                    int gravity = lp.gravity;
                    if (gravity == -1) {
                        gravity = Gravity.TOP | Gravity.START;
                    }

                    final int layoutDirection;
                    final int absoluteGravity;
                    final int verticalGravity;
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                        layoutDirection = getLayoutDirection();
                        absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                        verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
                    } else {
                        absoluteGravity = gravity;
                        verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
                    }

                    switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                        case Gravity.CENTER_HORIZONTAL:
                            childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                                    lp.leftMargin - lp.rightMargin;
                            break;
                        case Gravity.RIGHT:
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        case Gravity.LEFT:
                        default:
                            childLeft = parentLeft + lp.leftMargin;
                    }

                    switch (verticalGravity) {
                        case Gravity.TOP:
                            childTop = parentTop + lp.topMargin;
                            break;
                        case Gravity.CENTER_VERTICAL:
                            childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                                    lp.topMargin - lp.bottomMargin;
                            break;
                        case Gravity.BOTTOM:
                            childTop = parentBottom - height - lp.bottomMargin;
                            break;
                        default:
                            childTop = parentTop + lp.topMargin;
                    }

                    child.layout(childLeft, childTop, childLeft + width, childTop + height);
                    setChildPoint(child, childLeft, childTop);
                }
            }
        }

        if (!randomViews.isEmpty()) {
            for (View child : randomViews) {

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                Point point = calculatedViews.get(child);
                if (point == null) {
                    point = generateChildPoint(child);
                }

                int childLeft = point.x;
                int childTop = point.y;

                child.layout(childLeft, childTop, childLeft + width, childTop + height);
                setChildPoint(child, childLeft, childTop);
            }
        }
    }

    private Point generateChildPoint(View child) {
        Random random = new Random();
        while (true) {
            boolean available = true;
            Point point = new Point();
            point.set(random.nextInt(getWidth() - child.getMeasuredWidth()),
                    random.nextInt(getHeight() - child.getMeasuredHeight()));
            for (View item : calculatedViews.keySet()) {
                if (checkBump(child, item, point, calculatedViews.get(item))) {
                    available = false;
                    break;
                }
            }
            if (available) {
                return point;
            }
        }
    }

    private void setChildPoint(View child, int x, int y) {
        Point point = calculatedViews.get(child);
        if (point == null) {
            point = new Point();
            calculatedViews.put(child, point);
        }
        point.set(x, y);
    }

    public static class LayoutParams extends FrameLayout.LayoutParams {

        public static final int NORMAL = 0;
        public static final int RANDOM = 1;

        public int layoutMode = NORMAL;

        public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) {
            super(c, attrs);

            final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.SilkProduceView_Layout);
            layoutMode = a.getInt(R.styleable.SilkProduceView_Layout_layout_mode, NORMAL);
            a.recycle();
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(int width, int height, int gravity, int layoutMode) {
            super(width, height, gravity);
            this.layoutMode = layoutMode;
        }

        public LayoutParams(ViewGroup.LayoutParams lp) {
            super(lp);
        }
    }

}
